<?
/**
 * basedbo class - base class for database objects
 */
(basename($_SERVER['PHP_SELF']) == basename(__FILE__)) && die('Access Denied');

class HistoryTimelineDbo extends phpdbo {

/**
 * Placeholder for registered extensions
 * @see registerApi(), isApi()
 */
private $api = array();

/**
 * Is the current object type ID an auto_increment column?
 */
public $isAutoIncrement = true;

/**
 * Constructor
 * @param mixed $args
 */
public function __construct($args = null) {
	// register our extensions
	$this->registerApi('HistoryTimelineDbo');
	$this->registerApi('Category');
	$this->registerApi('CategoryDbo');
	$this->registerApi('Event');
	$this->registerApi('EventDbo');
	$this->registerApi('EventList');
	$this->registerApi('Media');
	$this->registerApi('MediaDbo');
	$this->registerApi('MimeType');
	$this->registerApi('MimeTypeDbo');
	$this->registerApi('Source');
	$this->registerApi('SourceDbo');
	$this->registerApi('Log');
	$this->registerApi('LogDbo');
	$this->registerApi('User');
	$this->registerApi('UserDbo');
	}

/**
 * Validate a date string.
 * @return bool
 */
public function _validateDateString($val = '') {
	if ($val == 'now()') { return true; }
	if (is_string($val) and strlen($val) !== 10) {
		return $this->validateDateTimeString($val);
		}
	if (!is_string($val) or strlen($val) !== 10) { return false; }
	if ($val == '0000-00-00') { return true; }
	return checkdate(substr($val,5,2),substr($val,8,2),substr($val,0,4));
	}

/**
 * Generic method to add currently populated object to database
 * @param bool $validate
 * @return bool true on success
 * @return string error message on failures
 */
public function add($validate=true) {
	// validate
	if (!($validate === false)) {
		$rc = $this->validate(true);
		if (!($rc === true)) { return $rc; }
		}
	if (!($this->requireDatabase() === true)) { return 'db connect failure'; }
	// Can we determine the idproperty?
	$idProp = $this->getIdProperty();
	if ($idProp === false) { return 'Cannot determine ID property.'; }
	// Generate the sql statement, only adding fields that exist...
	foreach($this->getPropertyList() as $prop) {
		$val = $this->getProperty($prop);
		if ($val === false) { continue; }
		$desc = $this->getPropertyDescription($prop);
		$field = $this->getPropertyField($prop);
		if (!is_string($field)) {
			return 'Failed to get field name for: ' . $desc;
			}
		$quoted = $this->getPropertyQuoted($prop);
		if (!is_bool($quoted)) {
			return 'Failed to determine if ' . $desc . ' is quoted.';
			}
		if ($quoted === false) {
			if (!is_numeric($val)) {
				return $desc . ' is not quoted and not numeric';
				}
			}
		// Handle unquoting date fields with now()...
		if ($this->getPropertyIsDate($prop)) {
			if ($val == 'now()') { $quoted = false; }
			}
		($quoted) ? $d = '"' : $d = '';
		$fl[] = $field;
		$vl[] = $d . $this->slasher($val) . $d;
		}
	// Something went horribly wrong
	if (count($fl) < 1) { return 'Properties were not properly populated.'; }
	if (!(count($fl) == count($vl))) { return 'field/value mismatch'; }
	// Generate the sql statement.
	$sql = 'insert into ' . $this->getDatabaseTable() . ' '
	. '(' . implode(',',$fl) . ') ' . 'values(' . implode(',',$vl) . ')';
	// Do the insert
	if ($this->db->db_query($sql) === false) {
		return 'SQL ERROR: ' . $this->db->db_error();
		}
	// Plug in the resulting id
	if ($this->isAutoIncrement) {
		$id = $this->db->db_insert_id();
		if (!is_numeric($id) or $id < 1) {
			return 'Failed to obtain ID after insert.';
			}
		} else {
		$id = $this->getProperty('Id');
		}
	$this->setProperty($idProp,$id);
	return true;
	}

/**
 * Is the specified attribute type quoted?
 * @param string attributeType
 * @return bool
 */
public function attributeTypeIsQuoted($attributeType = null) {
	switch($attributeType) {
		case 'money':
		case 'tinyint':
		case 'smallint':
		case 'int':
		case 'SelectList':
			return false;
		break;
		}
	return true;
	}

/**
 * Delete currently populated object by id.
 * @return string error message on failures
 * @return bool true on success
 */
public function delete() {
	if (!$this->populated) { return 'object not populated.'; }
	if (!($this->requireDatabase() === true)) { return 'db connect failure'; }
	// need the id property
	$idProp = $this->getIdProperty();
	$id = $this->getProperty($idProp);
	if ($id === false) { return 'id not set'; }
	$idField = $this->getPropertyField($idProp);
	$quoted = $this->getPropertyQuoted($idProp);
	$d = ($quoted) ? '"' : '';
	$sql = 'delete from ' . $this->getDatabaseTable() . ' '
	. 'where ' . $idField . ' = ' . $d . $this->slasher($id) . $d
	. ' limit 1';
	if ($this->db->db_query($sql) === false) {
		return 'SQL ERROR: ' . $this->db->db_error();
		}
	return true;
	}

/**
 * Delete a row by id
 * @param string $id
 * @return bool true on success
 * @return string error message on failure.
 */
public function deleteById($id = null) {
	if (!$this->validateString($id)) { return 'invalid id specified'; }
	$idProp  = $this->getIdProperty();
	$idField = $this->getPropertyField($idProp);
	$q = $this->getPropertyQuoted($idProp);
	if (!is_bool($q)) { return 'failed to determine if id prop is quoted'; }
	($q) ? $d = '"' : $d = '';
	if (!$q and !is_numeric($id)) { return 'non-numeric id specified'; }
	$sql = 'delete from ' . $this->getDatabaseTable() . ' where '
	. $idField . ' = ' . $d . addslashes($id) . $d . ' limit 1';
	if ($this->db->db_query($sql) === false) {
		return 'SQL ERROR: ' . $this->db->db_error();
		}
	return true;
	}

/**
 * Get datetime stamp from current database connection.
 * @return mixed
 *   string datetime (YYYY-MM-DD hh:mm:ss)
 *   bool false on failures
 */
public function getDateTime() {
	$sql = 'SELECT NOW() as dt';
	$q = $this->db->db_query($sql);
	if ($q === false) { return false; }
	return $this->db->db_result($q, 0, 0);
	}

/**
 * Get singleton database object instance, re-using current database connection.
 * @param string objectType
 * @param mixed args
 * @return mixed
 */
public function &getDbo($objectType = null, $args = null) {
	global $_dbi;
	if (!isset($_dbi) or !is_array($_dbi)) {
		$_dbi = array();
		}
	$err   = false;
	$class = false;
	if (!$this->isApi($objectType)) { return $err; }

	// Already have one instantiated...
	if (isset($_dbi[$objectType]->connected)) {
		return $_dbi[$objectType];
		}

	// Does the specified class actually exist?
	if (!class_exists($objectType, false)) { return $err; }

	// Create a new instance of it, assign it my database connection,
	// store it, return it.
	$_dbi[$objectType] = new $objectType($args);
	$_dbi[$objectType]->setDatabase($this->_databaseName);
	$_dbi[$objectType]->setDatabaseHost($this->_databaseHost);
	$_dbi[$objectType]->db = &$this->db;
	return $_dbi[$objectType];
	}

/**
 * Get array containing distinct property values from the current object type
 * @param string $property
 * @return array
 */
public function getDistinctPropertyValues($property = null) {
	$ret_ar = array();
	if (!$this->isProperty($property)) { return $ret_ar; }
	$this->searchReset();
	$this->setSearchSelect($property, true);
	$this->setSearchOrder($property);
	$q = $this->query();
	if (!is_array($q)) { return $ret_ar; }
	foreach($q as &$row) {
		$ret_ar[] = $row[$property];
		}
	unset($q);
	return $ret_ar;
	}

/**
 * Is the specified extension name registered?
 * @param string extensionName
 * @return bool
 */
private function isApi($extensionName = null) {
	if (!is_string($extensionName) or !strlen($extensionName)) { return false; }
	return (in_array($extensionName, $this->api));
	}

/**
 * Populate object from provided array, as long as it is not already populated
 */
public function populateFromArray(&$ar=null) {
	if ($this->populated) { return false; }
	foreach($this->getPropertyList() as $prop) {
		if (isset($ar[$prop])) {
			$this->setProperty($prop,$ar[$prop]);
			}
		}
	$this->populated = true;
	return true;
	}

/**
 * Populate object from POST data, overwriting existing properties.
 * @return bool
 */
public function populateFromPost() {
	if (!isset($_POST) or !is_array($_POST)) { return false; }
	foreach($this->getPropertyList() as $prop) {
		if (isset($_POST[$prop])) {
			$this->setProperty($prop,trim(stripslashes($_POST[$prop])));
			}
		}
	return true;
	}

/**
 * Register an extension (api) name
 * @param string extensionName
 * @return bool
 * @see isApi()
 */
private function registerApi($extensionName = null) {
	if (!is_string($extensionName) or !strlen($extensionName)) { return false; }
	if (in_array($extensionName, $this->api)) { return true; }
	$this->api[] = $extensionName;
	return true;
	}

/**
 * Does the specified table name exist in the current database?
 * @param string tableName
 * @return bool
 */
public function tableExists($tableName = null) {
	if (!is_string($tableName) or !strlen($tableName)) { return false; }
	$q = $this->db->db_list_tables($this->getDatabase());
	$ar = $this->db->query_hash_rows($q);
	if (!is_array($ar) or !count($ar)) { return false; }
	$tables = array();
	foreach($ar as $a) {
		foreach($a as $junk => $tbl) { $tables[] = $tbl; }
		}
	unset($ar);
	return (in_array($tableName, $tables));
	}

/**
 * Generic method to update currently populate object
 * @param bool $validate - pass it through validation first
 * @return string error message on failures
 * @return bool true on success.
 */
public function update($validate=true) {
	// validate first...
	if (!($validate === false)) {
		$rc = $this->validate();
		if (!($rc === true)) { return $rc; }
		}
	if (!($this->requireDatabase() === true)) { return 'db connect failure'; }
	// need the id property
	$idProp = $this->getIdProperty();
	$id = $this->getProperty($idProp);
	if ($id === false) { return 'id not set'; }
	$idField = $this->getPropertyField($idProp);
	// look up the current object for comparison
	$curObj = $this->queryById($id);
	if (!is_array($curObj)) { return 'failed to locate existing member'; }
	// Store actual updates here...
	$updates = array();
	foreach($this->getPropertyList() as $prop) {
		$val = $this->getProperty($prop);
		$desc = $this->getPropertyDescription($prop);
		if ($val === false) { continue; }
		if ($val == $curObj[$prop]) { continue; }
		// passed muster, add it to the updates array
		$f = $this->getPropertyField($prop);
		$quoted = $this->getPropertyQuoted($prop);
		// unquote date fields that need it....
		if ($this->getPropertyIsDate($prop)) {
			if (strtolower($val) == 'now()') { $quoted = false; }
			}
		($quoted) ? $d = '"' : $d = '';
		$updates[] = $f . '=' . $d . $this->slasher($val) . $d;
		}
	// Hmm?
	if (count($updates) < 1) { return true; }
	// Generate sql statement
	$d = ($this->getPropertyQuoted($idProp)) ? '"' : '';
	$sql = 'update ' . $this->getDatabaseTable() . ' '
	. 'set ' . implode(', ',$updates) . ' '
	. 'where ' . $idField . '=' . $d . $this->slasher($id) . $d . ' limit 1';
	// Do the update
	if ($this->db->db_query($sql) === false) {
		return 'SQL ERROR: ' . $this->db->db_error();
		}
	return true;
	}

/**
 * Generic validation of currently populated object.
 * @param bool $skipId - skip validation of id property (on adds)
 * @return bool true on success
 * @return string error message on failures
 */
public function validate($skipId=false) {
	if (!is_bool($skipId)) { return 'skipId argument is invalid'; }
	if (!($this->populated === true)) { return 'object is not populated'; }
	$props = $this->getPropertyList();
	if (!is_array($props) or count($props) < 1) {
		return 'no properties have been defined';
		}
	if ($skipId === false) {
		$idProp = $this->getIdProperty();
		if (!$this->isProperty($idProp)) {
			return 'cannot determine ID property';
			}
		}
	// loop through each property and validate non-empty values
	foreach($props as $prop) {
		$val = $this->getProperty($prop);
		$desc = $this->getPropertyDescription($prop);
		if ($val === false) { continue; }
		if ($this->getPropertyIsDate($prop)) {
			if (!($this->_validateDateString($val) === true)) {
				$m = 'prop(' . $desc . ') not valid date: '
				. $val;
				return $m;
				}
			continue;
			}
		$quoted = $this->getPropertyQuoted($prop);
		if (!is_bool($quoted)) {
			$m = 'prop(' . $desc . ') getPropertyQuoted()';
			return $m;
			}
		if ($quoted === false) {
			if (!is_numeric($val)) {
				$m = 'prop(' . $desc . ') non-numeric value: ' . $val;
				return $m;
				}
			}
		// check max length
		$len = strlen($val);
		$max = $this->getPropertyMaxLength($prop);
		if ($len > $max) {
			$m = 'prop(' . $desc . ') length of ' . $len
			. ' > max length of ' . $max;
			return $m;
			}
		}
	return true;
	}

/**
 * Validate a date/time string.
 * @param string val
 * @return bool
 */
public function validateDateTimeString($val = null) {
	if ($val == 'now()') { return true; }
	if (!is_string($val) or strlen($val) !== 19) { return false; }
	if ($val == '0000-00-00 00:00:00') { return true; }
	return checkdate(substr($val,5,2),substr($val,8,2),substr($val,0,4));
	}

/**
 * Verify specified arg is a string of at least 1 character.
 * @param string $txt
 * @return bool
 */
public function validateString($txt=null) {
	return (is_string($txt) and strlen($txt) > 0);
	}

}

?>
